package javatuning.ch3.nio;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * log日志上报组件: 写map 通道映射文件的一段区域
 *
 * 多进程写, 单进程上报, + channel高性能文件存储 + 班车制度
 * 数据统计接口调用非常频繁, 把数据发送到队列中, 一个死循环线程在监控这个队列,把他写入mmap,
 * 如果是第一次写入这个文件,启动定时器5分钟，5分钟停止写入文件, 文件的初始值是1M,写入前判断，如果当前
 * 的positon 加上要写入的内容超过了limit,从此刻的开始,再映射1M
 *
 */
public class WriteFileByMap extends Thread{
    private FileChannel fc;
    private RandomAccessFile randomAccessFile = null;
    CharBuffer charBuffer;
    BlockingQueue<Msg> queue = new LinkedBlockingQueue<>();
    long delay = 1000; // 延时多久
    Timer timer = new Timer();
    private AtomicInteger count = new AtomicInteger(0);
    private static final int LIMIT = 7; // 设置文件写入的最大值,最多只能写两次，超出从当前位置继续映射
    private String currentFileName;
    public WriteFileByMap() {
        initConfig("temp_"+count.getAndIncrement());
    }

    void initConfig(String fileName) {
        currentFileName = fileName;
        try {
            close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            randomAccessFile = new RandomAccessFile
                    (fileName, "rw");
            fc = randomAccessFile.getChannel();
            // 这里必须乘以2,考虑到中文字符占两个字节的问题
            charBuffer = fc.map(FileChannel.MapMode.READ_WRITE, 0, LIMIT*2)
                    .asCharBuffer();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 这里必须还是需要子线程
     */
    public void writen(Msg msg) {
        this.queue.offer(msg);
    }

    public void close() throws IOException {
        if (fc != null && fc.isOpen()) {
            fc.close();
        }
    }

    @Override
    public void run() {
        System.out.println("thread start ...");
        while (true) {
            Msg content = null;
            try {
                System.out.println(" take ...");
                content = queue.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (content != null) {
                try {
                    process(content);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void process(Msg msg)  throws IOException {
        // 已经到点,如果文件根本就没有写，可以忽略, 否则请关闭stream，然后新建文件
        if (msg.type == Msg.MSG_TIMEOUT) {
            System.out.println(" 到时间了 写入的大小:包含映射的内存没有的空格 = " + fc.size());
            if (charBuffer.position() > 0) {
                String newFileName = "temp_"+count.getAndIncrement();
                System.out.println(String.format("关闭文件 %s,重新创建文件 %s", currentFileName, newFileName));
                initConfig(newFileName);
            } else {
                System.out.println(" 文件还没有被写入，忽略 ");
            }
        } else {
            long starttime = System.currentTimeMillis();
            if (fc == null) {
                return;
            }
            if (!fc.isOpen()) {
                return;
            }
            int position = charBuffer.position();
            int limit = charBuffer.limit();
            System.out.println("position="+position+", limit:"+limit);
            if (position == 0) {
                System.out.println(currentFileName + " 第一次写入,启动定时器结束文件的写操作");
                timer.schedule(new MyTimerTask(), delay);
            }
            long needSize = position + msg.content.length();
            System.out.println("写文件前的当前长度 currentSize="+position + ", needSize="+needSize);
            if (needSize > LIMIT) { //
                System.out.println("超过预设值重新映射");
//                initConfig("temp_"+count.getAndIncrement());
                // 从FileChannel获取大小，不是那么正确，里面可能包含空格,不过log没有关系
                charBuffer = fc.map(FileChannel.MapMode.READ_WRITE, fc.size(), LIMIT*2)
                        .asCharBuffer();
            }
            charBuffer.put(msg.content);
            System.out.println("写文件后的 position="+charBuffer.position());
            System.out.println("testMappedWriteChar:"+(System.currentTimeMillis()-starttime)+"ms");
        }
    }

    class MyTimerTask extends TimerTask {
        @Override
        public void run() {
            writen(new Msg(Msg.MSG_TIMEOUT, null));
        }
    }

    static class Msg {
        public static final int MSG_NORMAL = 0;
        public static final int MSG_TIMEOUT = 1;
        public int type;
        public String content;

        public Msg(int type, String content) {
            this.type = type;
            this.content = content;
        }
    }
}
